/*
 * PositiveIntegerOnly.java
 *
 * Created on Feb 27, 2008
 *
 * Copyright(c) 2008 OpenOak Technologies 
 * Licensed under the OpenOak Learning License
 */

package com.openoak.textfield;

import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 *  Creates an Positive integer-only text field
 *
 *  @author Steven C. Daniel - Copyright (c) 2008
 *  @author OpenOak Technologies - www.openoak.com
 *  @version 1.0
 */
public class PositiveIntegerOnly extends Plain implements FocusListener {

    private boolean hasRange = false;
    private int minimumValue = 0;
    private int maximumValue = 0;
    private String rangeMessage = "";
    
    public PositiveIntegerOnly() {
        this.setDocument(new IntegerDocument());
        this.setHorizontalAlignment(PositiveIntegerOnly.RIGHT);
        this.addFocusListener(this);
        this.setInputVerifier(new RangeVerifier());
    }
    
    /**
     * Sets the input range for the integer field.  The default is any Positive Integer
     * 
     * @param minimumValue The minimum value to set
     * @param maximumValue The maximum value to set
     */
    public void setRange(int minimumValue, int maximumValue) {
        if ((minimumValue < maximumValue) && (minimumValue >= 0)) {
            hasRange = true;
            this.minimumValue = minimumValue;
            this.maximumValue = maximumValue;
            rangeMessage = "[" + minimumValue + "::" + maximumValue + "]";
        }
        
    }
    
    /**
     * Gets the value of the field as a positive integer. 
     * <p> NOTE:  If the field is blank, it will return a zero.  The program should
     * be prepared for this.  If you need to check for null values, you can call isNull().
     * 
     * @return the value of the field as an integer
     */
    public int getValue(){
        String theValue = this.getText();
        int value;
        
        if (theValue.length() < 1) {
            value = 0;
        } else {
            try {
                value = Integer.parseInt(theValue);
            } catch (java.lang.NumberFormatException exp) {
                value = 0;
            } 
        }
        return value;
    }
    
    /**
     * Sets the value of the text field, provided that a positive integer is sent
     * 
     * @param newValue The value to set
     */
    public void setValue (int newValue) {
        if (newValue >= 0) {
            super.setText(newValue + "");
        }
    }
    /**
     * Sets the value of the text field.  This will check to see if it's an integer
     * first.  If it's passed something that it can identify as an integer, it will
     * blank out the text field.
     * 
     * @param newText The value you wish to set
     */
    @Override
    public void setText(String newText){
        if (newText.length() > 0){
            try {
                int newInteger = Integer.parseInt(newText);
                super.setText(newInteger + "");
            } catch (java.lang.NumberFormatException exp){
                super.setText("");
            }
        } else {
            super.setText("");
        }
    }
    
    private void showMyMessage(String message) {
        this.showMessage(message);
    }
    
    private void showMyMessageWarning(String message) {
        this.showMessageWarning(message);
    }
    
    private void showMyMessageError(String message) {
        this.showMessageError(message);
    }
    
    private void hideMyMessage(){
        this.clearMessage();
    }
    
    /**
     * The 'document' that controls the text input
     */
    class IntegerDocument extends PlainDocument {
        private boolean validInput = false;
        private int currentValue = 0;
        
        public IntegerDocument(){
            
        }
        
        @Override
        public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException {
            System.out.println("insertString ");
            if (string == null) {
                return;
            } else {
                String newValue;
                
                int length = getLength();
                if (length == 0) {
                    newValue = string;
                } else {
                    String currentContent = getText(0, length);
                    StringBuffer currentBuffer = new StringBuffer(currentContent);
                    currentBuffer.insert(offset, string);
                    newValue = currentBuffer.toString();
                }
                currentValue = checkInput(newValue, offset);
                super.insertString(offset, string, attributes);
            }
        }
        
        @Override
        public void remove(int offset, int length) throws BadLocationException {
            int currentLength = getLength();
            String currentContent = getText(0, currentLength);
            String before = currentContent.substring(0, offset);
            String after = currentContent.substring(length + offset, currentLength);
            String newValue = before + after;
            currentValue = checkInput(newValue, offset);
            super.remove(offset, length);
        }
        
        public int checkInput(String proposedValue, int offset) throws BadLocationException {
            int proposedLength = proposedValue.length();
            validInput = false;
            if (proposedLength > 0) {
                boolean isOK = false;
                String regEx = "[0-9]";
                for (int i = 0; i < proposedLength; i++){
                    char checkChar = proposedValue.charAt(i);

                    Pattern maskPattern = Pattern.compile(regEx);
                    String maskMatch = String.valueOf(checkChar);
                    Matcher matcher = maskPattern.matcher(maskMatch);
                    if (matcher.find()) {
                        isOK = true;
                    } else {
                        isOK = false;
                        if (hasRange) {
                            showMyMessageWarning(rangeMessage + " Integer only");
                        } else {
                            showMyMessageWarning("Positive Integer only");
                        }
                        throw new BadLocationException(proposedValue, offset);
                    }
                    
                }
                if (isOK) {
                    try {
                        int tryValue = Integer.parseInt(proposedValue);
                    } catch (java.lang.NumberFormatException exp) {
                        isOK = false;
                        showMyMessageError ("Integer out of range.");
                    }
                }
                if (isOK) {
                    if (!hasRange) {
                        hideMyMessage();
                    } else {
                        if (verifyInRange(proposedValue)) {
                            showMyMessage(rangeMessage);
                        } else {
                            showMyMessageWarning(rangeMessage);
                        }
                    }
                }
                validInput = isOK;
            } else {
                return 0;
            }
            return 0;
        }
        public boolean isValidInput(){
            return validInput;
        }
    }

    public void focusGained(FocusEvent e) {
        super.focusGained(e);
        if (hasRange) {
            showMyMessage(rangeMessage);
        }
        renderValidText();
    }
    
    public void focusLost(FocusEvent e) {
        super.focusLost(e);
        try {
            int newValue = Integer.parseInt(this.getText());
            super.setText(newValue + "");
            hideMyMessage();
        } catch (java.lang.NumberFormatException exp){
            super.setText("");
        }
        
    }
    
    public class RangeVerifier extends InputVerifier{

        @Override
        public boolean verify(JComponent input) {
            JTextField tf = (JTextField)input;
            String myStringValue = tf.getText();
            return verifyInRange(myStringValue);
        }
        
    }
    
    private boolean verifyInRange(String value) {
        if (value.length() > 0) {
            
             if (hasRange) {
                try {
                    int myValue = Integer.parseInt(value);
                    if ((myValue >= minimumValue) && (myValue <= maximumValue)) {
                        return true;
                    } else {
                        showMyMessageError(rangeMessage + " Integer Range");
                        return false;
                    }
                } catch (java.lang.NumberFormatException exp) {
                    showMyMessageError(rangeMessage + " Integer Range");
                    return false;
                }
            } else {
                    try{
                        int myValue = Integer.parseInt(value);
                    } catch (java.lang.NumberFormatException exp) {
                        showMyMessageError("Integer out of range.");
                        return false;
                    }
                }
            return true;
            
        }
        return true;
    }
    
    @Override
    public boolean isValidText(){
        return ((IntegerDocument)this.getDocument()).isValidInput();
    }
}
